﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;

namespace Pg.BioMedics.SDR.Web.Security
{
    /// <summary>
    /// This HTTP module is designed to support basic HTTP authnetication with the 
    /// MembershipProvider defined in the appropriate configuration file section
    /// </summary>
    public class BasicAuthenticationModule : IHttpModule
    {
        #region Constants

        private const string Realm = "SDR Basic Authentication";
        private const string PrincipalType = "BasicAuthentication";

        #endregion

        #region Event handlers 

        /// <summary>
        /// Handles authentication request and provides functionality for 
        /// username / password validation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            string authStr = app.Request.Headers["Authorization"];// authStr = "Basic ZXh0ZXJuYWwxOmFsYW1ha290YQ==";
            if (authStr == null || authStr.Length == 0)
            {
                // No credentials; anonymous request
                return;
            }

            authStr = authStr.Trim();
            if (authStr.IndexOf("Basic", 0) != 0)
            {
                // Don't understand this header...we'll pass it along and 
                // assume someone else will handle it
                return;
            }

            string encodedCredentials = authStr.Substring(6);

            byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
            string s = new ASCIIEncoding().GetString(decodedBytes);

            string[] userPass = s.Split(new char[] { ':' });
            string username = userPass[0];
            string password = userPass[1];

            if (Membership.ValidateUser(username, password))
            {
                string[] roles = Roles.GetRolesForUser(username);
                app.Context.User = new GenericPrincipal(new GenericIdentity(username, PrincipalType), roles);
            }
            else
            {
                // Invalid credentials; deny access
                DenyAccess(app);
                return;
            }
        }

        /// <summary>
        /// Handles end request event. If response status code is 401 sends 
        /// basic authentication request header to the client.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnEndRequest(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            if (app.Response.StatusCode == 401)
            {
                string val = String.Format("Basic Realm=\"{0}\"", Realm);
                app.Response.AppendHeader("WWW-Authenticate", val);
            }
        }

        #endregion

        #region IHttpModule Members

        /// <summary>
        /// Provides module initialization. This initializer binds to AuthenticateRequest
        /// and EndRequest events of HttpApplication.
        /// </summary>
        /// <param name="context">HttpApplication context</param>
        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += new EventHandler(OnAuthenticateRequest);
            context.EndRequest += new EventHandler(OnEndRequest);
        }             

        public void Dispose()
        {
            GC.SuppressFinalize(this);    
        }

        #endregion

        #region Support methods

        private void DenyAccess(HttpApplication app)
        {
            app.Response.StatusCode = 401;
            app.Response.StatusDescription = "Access Denied";

            // Write to response stream as well, to give user visual 
            // indication of error during development
            app.Response.Write("401 Access Denied");

            app.CompleteRequest();
        }


        #endregion
    }
}
